﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace LearningBubbles
{
    /// <summary>
    /// A custom-made textbox containing the assignmenttext
    /// </summary>
    internal class Textbox : Microsoft.Xna.Framework.GameComponent, IObserver<Player>
    {
        private Texture2D texture2D;
        private Rectangle rectangle;
        private Color color;
        SpriteFont font;
        SpriteFont bFont;
        String text;

        public Textbox(Game game, Rectangle rect, string assignmentText, BubbleObservable bo)
            : base(game)
        {
            rectangle = rect;
            font = game.Content.Load<SpriteFont>("textFont");
            bFont = game.Content.Load<SpriteFont>("BoldFont");
            Initialize(assignmentText);
            bo.Subscribe(this);
        }

        private IDisposable unsubscriber;

        public virtual void Subscribe(IObservable<Player> provider)
        {
            if (provider != null)
                unsubscriber = provider.Subscribe(this);
        }

        public virtual void OnCompleted()
        {
            unsubscriber.Dispose();
        }

        public virtual void OnNext(Player value)
        {
            if (value.CollectedElements.Count != 0) replaceEmptySpace(
                 value.CollectedElements[value.CollectedElements.Count - 1].Value.ToString()
                 );
        }

        public virtual void OnError(Exception e)
        {
        }

        /// <summary>
        /// Initializes the textbox as its meant to be seen from the start
        /// </summary>
        public void Initialize(string assignmentText)
        {
            texture2D = Textures.GuiBackGround;

            color = Color.Transparent;

            text = assignmentText;

            toBold();

            text = WrapText(bFont, text, rectangle.Width - 10);

            base.Initialize();
        }

        /// <summary>
        /// Finds the first occurence of the symbol marking a question and inserts some commands for a regex to 
        /// understand later
        /// </summary>
        public void toBold()
        {
            int i = text.IndexOf("???");
            if(i >= 0)
            {
                text = text.Insert(i + 3, "\\o");
                text = text.Insert(i, "\\b");
            }
        }

        /// <summary>
        /// Removes the commands that will make a text bold
        /// </summary>
        public void fromBold()
        {
            int i = text.IndexOf("\\b");
            int j = text.IndexOf("\\o");
            if(i >= 0 && j >= 0)
            {
                text = text.Remove(j, 2);
                text = text.Remove(i, 2);
            }
        }

        /// <summary>
        /// Removes all lineswitches generated by the machine when the text changes
        /// </summary>
        public void removeAllGeneratedLineSwitches()
        {
            while(text.Contains("\\l"))
            {
                int i = text.IndexOf("\\l");
                text = text.Remove(i, 2);
            }
        }

        /// <summary>
        /// Replaces the first question occurence with a string given
        /// </summary>
        /// <param name="s"></param>
        public void replaceEmptySpace(string s)
        {
            int i = text.IndexOf("???");
            if(i >= 0)
            {
                text = text.Remove(i, 3);
                text = text.Insert(i, s);
                fromBold();
                toBold();
                removeAllGeneratedLineSwitches();
                text = WrapText(font, text, rectangle.Width - 10);
            }
        }

        /// <summary>
        /// Draws the textbox
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(texture2D, rectangle, color);
            DrawString(spriteBatch, text, font, bFont);
        }

        /// <summary>
        /// Makes a new string that can be editted and drawn by the DrawStringWithStyle method
        /// </summary>
        public void DrawString(SpriteBatch spriteBatch, string text1, SpriteFont spriteFont, SpriteFont boldFont)
        {
            string[] words = text1.Split(' ');
            
            String String2 = "";

            var sb = new StringBuilder();
            
            float lineHeight = boldFont.MeasureString(" ").Y;

            float spaceHeight = boldFont.MeasureString(" ").Y;

            foreach (var word in words)
            {
                String2 = String2.Insert(String2.Length, word + " ");  
            }
            DrawStringWithStyle(spriteBatch, font, new Vector2(rectangle.X + 5, rectangle.Y + 5), String2, bFont);
        }

        /// <summary>
        /// Draws the string
        /// </summary>
        public void DrawStringWithStyle(SpriteBatch batch, SpriteFont thisFont, Vector2 pos, string thisText, SpriteFont BoldFont)
        {
            // Splits the text up at all commands
            string[] paragraphs = Regex.Split(thisText, @"\\(b)|\\(o)|\\(l)|\\(p)");
            SpriteFont CurrentFont = font;
            float tempPosX = pos.X;
            float tempPosY = pos.Y;

            //This for loop runs through the list made from splitting the text up, drawing all parts which isnt
            //a command and adding functionality for each command. Commands will allways be found on uneven spots.
            for (int i = 0; i < paragraphs.Length; i++)
            {
                if(tempPosY <= rectangle.Height)
                {
                    batch.DrawString(CurrentFont, paragraphs[i], new Vector2(tempPosX, tempPosY), Color.Black);    
                }
                else
                {
                    break;
                }
    
                if (i + 1 < paragraphs.Length)
                {
                    tempPosX += CurrentFont.MeasureString(paragraphs[i]).X;

                    i++;

                    //Says what the current commands function is
                    //o is that the following text should be normal font
                    //b is that the following text should be a bold font
                    //l is that there will be a system generated lineswitch (because of missing space)
                    //p is that the user has inserted a lineswitch
                    switch (char.ToLower(paragraphs[i][0]))
                    {
                        case 'o': CurrentFont = font; break;
                        case 'b': CurrentFont = BoldFont; break;
                        case 'l':
                            tempPosY += CurrentFont.MeasureString(" ").Y;
                            tempPosX = pos.X;
                            break;
                        case 'p':
                            tempPosY += CurrentFont.MeasureString(" ").Y;
                            tempPosX = pos.X;
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Wraps the text to find out where there has to be lineswitches
        /// </summary>
        public string WrapText(SpriteFont spriteFont, string text1, float maxLineWidth)
        {
            string[] words = text1.Split(' ');

            var sb = new StringBuilder();

            float lineWidth = 0f;

            float spaceWidth = spriteFont.MeasureString(" ").X;

            for (int i = 0; i < words.Length; i++)
            {
                Vector2 size = measureStringAfterRemovingSymbols(words[i], spriteFont);
                
                //Converts all \r\n to \\p so the regex in DrawStringWithStyle will understand that its a command
                if (words[i].Contains("\r\n") || words[i].Contains("\\p"))
                {
                    int occ = CountStringOccurrences(words[i], "\r\n");
                        for (int j = 0; j < occ; j++)
                        {
                            int index = words[i].IndexOf("\r\n");
                            words[i] = words[i].Remove(index, 2);
                            words[i] = words[i].Insert(index, "\\p");
                        }
                    lineWidth = size.X + spaceWidth;
                }

                if (lineWidth + size.X < maxLineWidth)
                {
                    sb.Append(words[i] + " ");
                    lineWidth += size.X + spaceWidth;
                }
                else
                {
                    sb.Append("\\l" + words[i] + " ");
                    lineWidth = size.X + spaceWidth;
                }
            }
            return sb.ToString();
        }

        //Counts how many occurences of one string there is inside another
        public static int CountStringOccurrences(string text, string pattern)
        {
             int count = 0;
             int i = 0;
             while ((i = text.IndexOf(pattern, i)) != -1)
             {
                i += pattern.Length;
                count++;
             }
             return count;
        }

        /// <summary>
        /// Measures the length of a string after removing the length of any commands
        /// </summary>
        public static Vector2 measureStringAfterRemovingSymbols(string s, SpriteFont spriteF)
        {
            if (s.Contains("\r\n") || s.Contains("\\b") || s.Contains("\\o") || s.Contains("\\p"))
            {
                int occ1 = CountStringOccurrences(s, "\r\n");
                int occ2 = CountStringOccurrences(s, "\\b");
                int occ3 = CountStringOccurrences(s, "\\o");
                int occ4 = CountStringOccurrences(s, "\\p");
                for (int j = 0; j < occ2; j++)
                {
                    int i = s.IndexOf("\\b");
                    s = s.Remove(i, 2);
                }
                for (int j = 0; j < occ3; j++)
                {
                    int i = s.IndexOf("\\o");
                    s = s.Remove(i, 2);
                }
                if (occ1 > 0)
                {
                    int i = s.LastIndexOf("\r\n");
                    s = s.Substring(i + 2);
                    return spriteF.MeasureString(s);
                }
                if (occ4 > 0)
                {
                    int i = s.LastIndexOf("\\p");
                    s = s.Substring(i + 2);
                    return spriteF.MeasureString(s);
                }
                return spriteF.MeasureString(s);
            }
            else
                return spriteF.MeasureString(s);
        }
    }
}
